package com.nsy.shortlink.project.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.nsy.shortlink.project.common.convention.exception.ClientException;
import com.nsy.shortlink.project.common.convention.exception.ServiceException;
import com.nsy.shortlink.project.common.enums.VailDateTypeEnum;
import com.nsy.shortlink.project.config.GotoDomainWhiteListConfiguration;
import com.nsy.shortlink.project.dao.entity.*;
import com.nsy.shortlink.project.dao.mapper.*;
import com.nsy.shortlink.project.dto.biz.ShortLinkStatsRecordDTO;
import com.nsy.shortlink.project.dto.req.ShortLinkCreateReqDTO;
import com.nsy.shortlink.project.dto.req.ShortLinkPageReqDTO;
import com.nsy.shortlink.project.dto.req.ShortLinkUpdateReqDTO;
import com.nsy.shortlink.project.dto.resp.*;
import com.nsy.shortlink.project.mq.producer.DelayShortLinkStatsProducer;
import com.nsy.shortlink.project.mq.producer.ShortLinkStatsSaveProducer;
import com.nsy.shortlink.project.service.LinkStatsTodayService;
import com.nsy.shortlink.project.service.ShortLinkService;
import com.nsy.shortlink.project.toolKit.HashUtil;
import com.nsy.shortlink.project.toolKit.LinkUtil;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.nsy.shortlink.project.common.constant.RedisKeyConstant.*;
import static com.nsy.shortlink.project.common.constant.ShortLinkConstant.AMAP_REMOTE_URL;


/**
* @author 宁舒意
* @description 针对表【t_link】的数据库操作Service实现
* @createDate 2024-10-05 10:56:14
*/
@Service
@RequiredArgsConstructor
public class ShortLinkServiceImpl extends ServiceImpl<ShortLinkMapper, ShortLinkDO>
    implements ShortLinkService {
    private final RBloomFilter<String> shortUriCreateCachePenetrationBloomFilter;
    private final ShortLinkGotoMapper shortLinkGotoMapper;

    private final StringRedisTemplate stringRedisTemplate;
    private final RedissonClient redissonClient;

    private final LinkAccessStatsMapper linkAccessStatsMapper;

    private final LinkLocaleStatsMapper linkLocaleStatsMapper;

    private final LinkOsStatsMapper linkOsStatsMapper;
    private final LinkBrowserStatsMapper linkBrowserStatsMapper;

    private final LinkAccessLogsMapper linkAccessLogsMapper;

    private final LinkDeviceStatsMapper linkDeviceStatsMapper;

    private final LinkNetworkStatsMapper linkNetworkStatsMapper;

    private final LinkStatsTodayMapper linkStatsTodayMapper;

    private final LinkStatsTodayService linkStatsTodayService;
    private final DelayShortLinkStatsProducer delayShortLinkStatsProducer;

    private final GotoDomainWhiteListConfiguration gotoDomainWhiteListConfiguration;

    private final ShortLinkStatsSaveProducer shortLinkStatsSaveProducer;
    @Value("${short-link.stats.locale.amap-key}")
    private String statsLocaleAmapKey;

    @Value("${short-link.domain.default}")
    private String createShortLinkDefaultDomain;


    @Transactional(rollbackFor = Exception.class)
    @Override
    public ShortLinkCreateRespDTO createShortLink(ShortLinkCreateReqDTO requestParam) {
        verificationWhitelist(requestParam.getOriginUrl());
        String shortUri= generateSuffix(requestParam);
        ShortLinkDO shortLinkDO = BeanUtil.toBean(requestParam, ShortLinkDO.class);
        //创建全路径
        String fullShortUrl = StrBuilder
                .create(createShortLinkDefaultDomain)
                .append('/')
                .append(shortUri)
                .toString();

        shortLinkDO.setFullShortUrl(fullShortUrl);
        shortLinkDO.setShortUri(shortUri);
        shortLinkDO.setEnableStatus(0);
        //为了测试性能，把这个获取网站图标先注释掉
        //shortLinkDO.setFavicon(getFavicon(requestParam.getOriginUrl()));
        shortLinkDO.setFavicon( null);
        shortLinkDO.setTotalPv(0);
        shortLinkDO.setTotalUv(0);
        shortLinkDO.setTotalUip(0);
        shortLinkDO.setDelTime(0L);

        shortLinkDO.setDomain(createShortLinkDefaultDomain);


        ShortLinkGotoDO build = ShortLinkGotoDO.builder()
                .fullShortUrl(shortLinkDO.getFullShortUrl())
                .gid(shortLinkDO.getGid())
                .build();

        //这里的insert有可能报异常，因为有唯一索引
        try {
            baseMapper.insert(shortLinkDO);
            shortLinkGotoMapper.insert(build);

        }catch (DuplicateKeyException e){
            //就说明数据库中肯定有这个短链接了，但是前面的布隆过滤器发生了误判，所以这里要把这个短链接加入布隆过滤器，然后抛出异常
            if (!shortUriCreateCachePenetrationBloomFilter.contains(fullShortUrl)) {
                //这里为什么要判断一下再加呢？
                shortUriCreateCachePenetrationBloomFilter.add(fullShortUrl);
            }
            throw new ServiceException(String.format("短链接：%s 生成重复", fullShortUrl));

        }

        //缓存预热
        //创建短链接的时候将短链接和原始链接存redis，设置有效期为用户传的有效期
        stringRedisTemplate.opsForValue().set(
                String.format(GOTO_SHORT_LINK_KEY,fullShortUrl),
                shortLinkDO.getOriginUrl(),
                LinkUtil.getLinkCacheValidTime(requestParam.getValidDate()), TimeUnit.MILLISECONDS);
        shortUriCreateCachePenetrationBloomFilter.add(createShortLinkDefaultDomain+'/'+shortUri);
        return ShortLinkCreateRespDTO.builder()
                .gid(requestParam.getGid())
                .fullShortUrl("http://"+shortLinkDO.getFullShortUrl())
                .originUrl(requestParam.getOriginUrl())
                .build();
    }


    /**
     * 生成短链接后缀
    **/
    private String generateSuffix(ShortLinkCreateReqDTO requestParam){


        String originUrl = requestParam.getOriginUrl();
        String shortUri = HashUtil.hashToBase62(originUrl);


        int count =0;
        while (shortUriCreateCachePenetrationBloomFilter.contains(createShortLinkDefaultDomain+'/'+shortUri)){
            //如果存在的话就重试
            count++;
            originUrl += UUID.randomUUID().toString();
            shortUri =HashUtil.hashToBase62(originUrl);
            if(count>=10){
                throw new ServiceException("创建短链接达到测试上限");
            }
        }
        //布隆过滤器判断不存在就肯定不存在，除非是并发场景下，有可能2个相同的短链接去插入数据库，插入之前都判断的是玻璃过滤器中不存在，但是最终只能又一个数据被插入，另一个数据由于唯一索引会报错
        //这里就当作不存在来处理，如果报异常了再在catch里面处理就行了
        return shortUri;


    }


    @Override
    public IPage<ShortLinkPageRespDTO> pageShortLink(ShortLinkPageReqDTO requestParam) {
        IPage<ShortLinkDO> resultPage = baseMapper.pageLink(requestParam);
        return resultPage.convert(each->{
            ShortLinkPageRespDTO shortLinkPageRespDTO = BeanUtil.toBean(each, ShortLinkPageRespDTO.class);
            shortLinkPageRespDTO.setDomain("http://"+shortLinkPageRespDTO.getDomain());
            return shortLinkPageRespDTO;
        });
    }


    @Override
    public List<ShortLinkGroupCountQueryRespDTO> listShortLinkGroup(List<String> requestParam) {
        QueryWrapper<ShortLinkDO> shortLinkDOQueryWrapper = Wrappers.query(new ShortLinkDO())
                .select("gid", "count(*) as 'shortLinkCount'")
                .in("gid", requestParam)
                .eq("enable_status", 0)
                .eq("del_flag", 0)
                .eq("del_time", 0L)
                .groupBy("gid");
        List<Map<String, Object>> shortLinkDOList = baseMapper.selectMaps(shortLinkDOQueryWrapper);
        return BeanUtil.copyToList(shortLinkDOList, ShortLinkGroupCountQueryRespDTO.class);

    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateShortLink(ShortLinkUpdateReqDTO requestParam) {
        verificationWhitelist(requestParam.getOriginUrl());
        /**
         * 这个方法4个作用
         * 1.可以修改该短链接的分组（改gid）
         * 2.可以改有效期类型和有效期
         * 3.改描述
         * 4.改要跳转到的链接
        **/
        //1.先根据原来的gid去表里查，没有记录的话就直接抛出异常
        //2.判断gid是否要修改，不改gid的话就直接update，要改了话就要先把旧的gid对应的记录删除，再用新的gid执行insert操作，记得加事务
        //gid不是全局唯一吗？，FullShortUrl可以修改吗？要是可以修改，这里还怎么查得到
        //FullShortUrl是前端隐式传参的，不允许修改
        LambdaQueryWrapper<ShortLinkDO> eq = Wrappers.lambdaQuery(ShortLinkDO.class)
                .eq(ShortLinkDO::getGid, requestParam.getOriginGid())
                .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                .eq(ShortLinkDO::getDelFlag, 0)
                .eq(ShortLinkDO::getEnableStatus, 0);
        ShortLinkDO shortLinkDO = baseMapper.selectOne(eq);

        if(shortLinkDO==null){
            throw new ClientException("短链接记录不存在");
        }

        if(Objects.equals(requestParam.getOriginGid(), requestParam.getGid())){
            //如果没有修改分组
            LambdaUpdateWrapper<ShortLinkDO> updateWrapper = Wrappers.lambdaUpdate(ShortLinkDO.class)
                    .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                    .eq(ShortLinkDO::getGid, requestParam.getGid())
                    .eq(ShortLinkDO::getDelFlag, 0)
                    .eq(ShortLinkDO::getEnableStatus, 0)
                    .set(Objects.equals(requestParam.getValidDateType(), VailDateTypeEnum.PERMANENT.getType()), ShortLinkDO::getValidDate, null);
            baseMapper.update(BeanUtil.toBean(requestParam,ShortLinkDO.class),updateWrapper);

        }else {
            //如果修改了gid
            RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(String.format(LOCK_GID_UPDATE_KEY, requestParam.getFullShortUrl()));
            RLock rLock = readWriteLock.writeLock();
//            if (!rLock.tryLock()) {
//                throw new ServiceException("短链接正在被访问，请稍后再试...");
//            }
            rLock.lock();
            try {
                //首先删除之前的短链接记录
                LambdaQueryWrapper<ShortLinkDO> delete = Wrappers.lambdaQuery(ShortLinkDO.class)
                        .eq(ShortLinkDO::getGid, requestParam.getOriginGid())
                        .eq(ShortLinkDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkDO::getDelFlag, 0)
                        .eq(ShortLinkDO::getEnableStatus, 0);
                baseMapper.delete(delete);

                //插入改完分组等信息的新短链接记录
                ShortLinkDO insertShortLink = ShortLinkDO.builder()
                        .domain(shortLinkDO.getDomain())
                        .originUrl(requestParam.getOriginUrl())
                        .gid(requestParam.getGid())
                        .createdType(shortLinkDO.getCreatedType())
                        .validDateType(requestParam.getValidDateType())
                        .validDate(requestParam.getValidDate())
                        .describe(requestParam.getDescribe())
                        .shortUri(shortLinkDO.getShortUri())
                        .enableStatus(shortLinkDO.getEnableStatus())
                        .fullShortUrl(shortLinkDO.getFullShortUrl())
                        .build();
                baseMapper.insert(insertShortLink);

                //更新goto表
                LambdaQueryWrapper<ShortLinkGotoDO> linkGotoQueryWrapper = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
                        .eq(ShortLinkGotoDO::getFullShortUrl, requestParam.getFullShortUrl())
                        .eq(ShortLinkGotoDO::getGid, requestParam.getOriginGid());
                ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(linkGotoQueryWrapper);
                shortLinkGotoMapper.delete(linkGotoQueryWrapper);
                shortLinkGotoDO.setGid(requestParam.getGid());
                shortLinkGotoMapper.insert(shortLinkGotoDO);


            }finally {
                rLock.unlock();
            }



        }

        /**
         * 关于缓存的处理
         * 1.如果把参数里的短链接改成过期时间了，就要把redis里面对应的可以跳转的键值对删除掉
         * 2.如果把一个原本过期了的短链接改成永久有效或者有效期在当前时间之后的（即有效的），就要把空key从redis中删掉
        **/
        if (!Objects.equals(shortLinkDO.getValidDateType(), requestParam.getValidDateType())
                || !Objects.equals(shortLinkDO.getValidDate(), requestParam.getValidDate())
                || !Objects.equals(shortLinkDO.getOriginUrl(), requestParam.getOriginUrl())) {
            //如果对有效期类型或者有效期进行了修改
            stringRedisTemplate.delete(String.format(GOTO_SHORT_LINK_KEY,requestParam.getFullShortUrl()));

            if(Objects.equals(requestParam.getValidDateType(),VailDateTypeEnum.PERMANENT.getType())||requestParam.getValidDate().after(new Date())){
                stringRedisTemplate.delete(String.format(GOTO_IS_NULL_SHORT_LINK_KEY,requestParam.getFullShortUrl()));
            }





        }






    }

    @Override
    public void restoreUri(String shortUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
        //1.拼接完整短链接
        String serverName = request.getServerName();
        String serverPort = Optional.of(request.getServerPort())
                .filter(each -> !Objects.equals(each, 80))
                .map(String::valueOf)
                .map(each -> ":" + each)
                .orElse("");
        String fullShortUrl = serverName + serverPort + "/" + shortUri;


        //2.在redis获取原始链接
        String originUrl = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
        if(StrUtil.isAllNotBlank(originUrl)){
            //3.如果缓存中有就直接跳转
            shortLinkStats(buildLinkStatsRecordAndSetUser(fullShortUrl, request, response));
            response.sendRedirect(originUrl);
            return;
        }
        //4.如果缓存中没有查到，先在布隆过滤器中查一下，没有的话就说明数据库中肯定没有
        if(!shortUriCreateCachePenetrationBloomFilter.contains(fullShortUrl)){
            //5.布隆过滤器中没有就说明数据库中肯定没有,直接返回
            response.sendRedirect("/page/notfound");
            return;
        }

        //6.如果布隆过滤器判断为存在，这个"存在"是3种可能（真的存在，布隆过滤器误判，存在但是以及过期变成空key了）
        //7.判断这个短链接是否为空key，用GOTO_IS_NULL_SHORT_LINK_KEY拼接，
        //这里有可能是空key，因为该短链接可能已经过期了（在数据库中设置的有效期过了）
        String isNullKey = stringRedisTemplate.opsForValue().get(String.format(GOTO_IS_NULL_SHORT_LINK_KEY, fullShortUrl));

        if(StrUtil.isNotBlank(isNullKey)){
            //如果查到在redis中存在就说明是空key，直接return
            response.sendRedirect("/page/notfound");
            return;
        }
        //布隆过滤器判断说有这个key，如果不是空key，但是redis中用GOTO_SHORT_LINK_KEY拼接得到的key又查不到，说明是布隆过滤器误判了

        //7.获取分布式锁,所有恶意请求阻塞在这
        RLock lock = redissonClient.getLock(String.format(LOCK_GOTO_SHORT_LINK_KEY, fullShortUrl));
        lock.lock();
        try {
            //双重判定
            String originUrlTWO = stringRedisTemplate.opsForValue().get(String.format(GOTO_SHORT_LINK_KEY, fullShortUrl));
            if(StrUtil.isAllNotBlank(originUrlTWO)){
                //缓存中有就说明被第一个线程写入缓存了，直接跳转

                shortLinkStats(buildLinkStatsRecordAndSetUser(fullShortUrl, request, response));
                response.sendRedirect(originUrlTWO);
                return;
            }
            //
            if(Objects.equals(stringRedisTemplate.opsForValue().get(String.format(GOTO_IS_NULL_SHORT_LINK_KEY, fullShortUrl)), "-")){
                //说明是空key，直接return
                response.sendRedirect("/page/notfound");
                return;

            }


            //如果在goto表查到没有该短链接的数据，就存空key到redis
            LambdaQueryWrapper<ShortLinkGotoDO> eq = Wrappers.lambdaQuery(ShortLinkGotoDO.class)
                    .eq(ShortLinkGotoDO::getFullShortUrl, fullShortUrl);

            ShortLinkGotoDO shortLinkGotoDO = shortLinkGotoMapper.selectOne(eq);
            if(shortLinkGotoDO==null){//
                stringRedisTemplate.opsForValue().set(
                        String.format(GOTO_IS_NULL_SHORT_LINK_KEY,fullShortUrl),
                        "-");
                response.sendRedirect("/page/notfound");
                return;
            }

            //得到路由表，即得到对应的gid了，就可以开始查原始链接了
            LambdaQueryWrapper<ShortLinkDO> shortLinkDOWrapper = Wrappers.lambdaQuery(ShortLinkDO.class)
                    .eq(ShortLinkDO::getGid, shortLinkGotoDO.getGid())
                    .eq(ShortLinkDO::getFullShortUrl,fullShortUrl)
                    .eq(ShortLinkDO::getDelFlag,0)
                    .eq(ShortLinkDO::getEnableStatus,0);
            ShortLinkDO shortLinkDO = baseMapper.selectOne(shortLinkDOWrapper);

            //如果没查到短链接记录，或者如果短链接是临时有效的并且已经过期，就在redis里面设置该key为空值，然后return
            if(shortLinkDO==null||(shortLinkDO.getValidDate()!=null&&shortLinkDO.getValidDate().before(new Date()))){
                stringRedisTemplate.opsForValue().set(String.format(GOTO_IS_NULL_SHORT_LINK_KEY, fullShortUrl), "-", 30, TimeUnit.MINUTES);
                response.sendRedirect("/page/notfound");
                return;
            }

            //在数据库查到记录了
            //将短链接对应的原始链接加到缓存中,以及将key存进布隆过滤器
            stringRedisTemplate.opsForValue().set(String.format(GOTO_SHORT_LINK_KEY,fullShortUrl),
                    shortLinkDO.getOriginUrl(),
                    LinkUtil.getLinkCacheValidTime(shortLinkDO.getValidDate()), TimeUnit.MILLISECONDS);
            shortUriCreateCachePenetrationBloomFilter.add(fullShortUrl);

            shortLinkStats(buildLinkStatsRecordAndSetUser(fullShortUrl, request, response));
            response.sendRedirect(shortLinkDO.getOriginUrl());



        }finally {
            lock.unlock();

        }


    }


    private ShortLinkStatsRecordDTO buildLinkStatsRecordAndSetUser(String fullShortUrl, ServletRequest request, ServletResponse response) {
        AtomicBoolean uvFirstFlag = new AtomicBoolean();
        Cookie[] cookies = ((HttpServletRequest) request).getCookies();
        AtomicReference<String> uv = new AtomicReference<>();
        Runnable addResponseCookieTask = () -> {
            uv.set(cn.hutool.core.lang.UUID.fastUUID().toString());
            Cookie uvCookie = new Cookie("uv", uv.get());
            uvCookie.setMaxAge(60 * 60 * 24 * 30);
            uvCookie.setPath(StrUtil.sub(fullShortUrl, fullShortUrl.indexOf("/"), fullShortUrl.length()));
            ((HttpServletResponse) response).addCookie(uvCookie);
            uvFirstFlag.set(Boolean.TRUE);
            stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UV_KEY+ fullShortUrl, uv.get());
        };
        if (ArrayUtil.isNotEmpty(cookies)) {
            Arrays.stream(cookies)
                    .filter(each -> Objects.equals(each.getName(), "uv"))
                    .findFirst()
                    .map(Cookie::getValue)
                    .ifPresentOrElse(each -> {
                        uv.set(each);
                        Long uvAdded = stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UV_KEY + fullShortUrl, each);
                        uvFirstFlag.set(uvAdded != null && uvAdded > 0L);
                    }, addResponseCookieTask);
        } else {
            addResponseCookieTask.run();
        }
        String remoteAddr = LinkUtil.getActualIp(((HttpServletRequest) request));
        String os = LinkUtil.getOs(((HttpServletRequest) request));
        String browser = LinkUtil.getBrowser(((HttpServletRequest) request));
        String device = LinkUtil.getDevice(((HttpServletRequest) request));
        String network = LinkUtil.getNetwork(((HttpServletRequest) request));
        Long uipAdded = stringRedisTemplate.opsForSet().add(SHORT_LINK_STATS_UIP_KEY + fullShortUrl, remoteAddr);
        boolean uipFirstFlag = uipAdded != null && uipAdded > 0L;
        return ShortLinkStatsRecordDTO.builder()
                .fullShortUrl(fullShortUrl)
                .uv(uv.get())
                .uvFirstFlag(uvFirstFlag.get())
                .uipFirstFlag(uipFirstFlag)
                .remoteAddr(remoteAddr)
                .os(os)
                .browser(browser)
                .device(device)
                .network(network)
                .build();
    }
    @SneakyThrows
    private String getFavicon(String url) {
        //创建URL对象
        URL targetUrl = new URL(url);
        //打开连接
        HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
        // 禁止自动处理重定向
        connection.setInstanceFollowRedirects(false);
        // 设置请求方法为GET
        connection.setRequestMethod("GET");
        //连接
        connection.connect();
        //获取响应码
        int responseCode = connection.getResponseCode();
        // 如果是重定向响应码
        if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
            //获取重定向的URL
            String redirectUrl = connection.getHeaderField("Location");
            //如果重定向URL不为空
            if (redirectUrl != null) {
                // 创建新的URL对象
                URL newUrl = new URL(redirectUrl);//打开新的连接
                connection = (HttpURLConnection) newUrl.openConnection();//设置请求方法为GET
                connection.setRequestMethod("GET");//连接
                connection.connect();//获取新的响应码
                responseCode = connection.getResponseCode();
            }
        }
        if (HttpURLConnection.HTTP_OK == responseCode) {
            Document document = Jsoup.connect(url).get();
            Element faviconLink = document.select("link[rel~=(?i)^(shortcut )?icon]").first();
            if (faviconLink != null) {
                return faviconLink.attr("abs:href");
            }
        }
        return null;
    }




    @Override
    public void shortLinkStats( ShortLinkStatsRecordDTO statsRecord) {
        Map<String, String> producerMap = new HashMap<>();
        producerMap.put("statsRecord", JSON.toJSONString(statsRecord));
        shortLinkStatsSaveProducer.send(producerMap);
    }





    private void verificationWhitelist(String originUrl) {
        Boolean enable = gotoDomainWhiteListConfiguration.getEnable();
        if (enable == null || !enable) {
            return;
        }
        String domain = LinkUtil.extractDomain(originUrl);
        if (StrUtil.isBlank(domain)) {
            throw new ClientException("跳转链接填写错误");
        }
        List<String> details = gotoDomainWhiteListConfiguration.getDetails();
        if (!details.contains(domain)) {
            throw new ClientException("演示环境为避免恶意攻击，请生成以下网站跳转链接：" + gotoDomainWhiteListConfiguration.getNames());
        }
    }


}




